﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net.Http;
using UtilZ.Dotnet.Ex.Base;
using UtilZ.Dotnet.Ex.Cache;
using UtilZ.Dotnet.Ex.ZLog;

namespace UtilZ.Dotnet.Ex.Communication.Net.Web
{
    /// <summary>
    /// web站点中间件
    /// </summary>
    public class WebSiteMiddleware : MiddlewareAbs
    {
        /// <summary>
        /// web站点配置
        /// </summary>
        private readonly WebSiteConfig _config;

        /// <summary>
        /// key:url;value:filePath
        /// </summary>
        private readonly ObjectCache _urlSourceFilePathCache = new ObjectCache();

        /// <summary>
        /// 构造函数
        /// </summary>
        /// <param name="config">web站点配置</param>
        public WebSiteMiddleware(WebSiteConfig config)
            : base()
        {
            if (config == null)
            {
                throw new ArgumentNullException(nameof(config));
            }

            this._config = config;
        }


        /// <summary>
        /// 尝试调用中间件处理[已处理返回true,否则返回false]
        /// </summary>
        /// <param name="request"></param>
        /// <param name="response">http响应</param>
        /// <returns>已处理返回true,否则返回false</returns>
        protected override bool PrimitiveTryInvoke(HttpRequest request, out IHttpResponse response)
        {
            response = null;

            string filePath = null;
            var resourceProvider = this._config.ResourceProvider;
            bool getResourceResult = false;
            if (resourceProvider != null)
            {
                getResourceResult = resourceProvider.TryGetResource(this._config, request, out filePath, out response);
                if (getResourceResult && response != null)
                {
                    return true;
                }
            }

            if (!getResourceResult)
            {
                filePath = this.GetPageFilePath(request);
            }

            if (string.IsNullOrWhiteSpace(filePath) || !File.Exists(filePath))
            {
                //ZLoger.Warn($"请求的文件\"{filePath}\"不存在.原始请示请求url:\"{request.OriginUrl}\"");
                return false;
            }

            var httpResponse = new HttpResponse(request.Connection);
            response = httpResponse;

            try
            {                
                if (request.Headers.TryGetValue(HttpRequestHeaders.Range, out string rangeStr))
                {
                    //请求中有携带部分请求,按部分请求处理
                    if (string.IsNullOrWhiteSpace(rangeStr))
                    {
                        ZLoger.Warn($"请求范围\"{HttpRequestHeaders.Range}\"不能为空");
                        httpResponse.StatusCode = HttpStatusCodes.BadRequest;
                        return true;
                    }

                    List<HttpRequestRange> reqRangeList = HttpRequest.ParseHttpRequestRanges(rangeStr);
                    if (reqRangeList == null || reqRangeList.Count == 0)
                    {
                        ZLoger.Warn($"部分请求范围值\"{rangeStr}\"无效");
                        httpResponse.StatusCode = HttpStatusCodes.BadRequest;
                        return true;
                    }

                    httpResponse.RequestRangeList = reqRangeList;
                    httpResponse.StatusCode = HttpStatusCodes.PartialContent;
                }

                httpResponse.SetFile(filePath);
            }
            catch (ArgumentOutOfRangeException aoorex)
            {
                ZLoger.Warn(aoorex);
                httpResponse.StatusCode = HttpStatusCodes.RequestedRangeNotSatisfiable;
            }
            catch (HttpRequestException hex)
            {
                ZLoger.Warn(hex);
                httpResponse.StatusCode = HttpStatusCodes.RequestEntityTooLarge;
            }
            catch (Exception ex)
            {
                ZLoger.Warn(ex);
                httpResponse.StatusCode = HttpStatusCodes.InternalServerError;
            }

            return true;
        }


        /// <summary> 
        /// 获取请求文件路径[无效返回null,有效返回文件路径]
        /// </summary>
        /// <param name="request">页面请求</param>
        /// <returns>无效返回null,有效返回文件路径</returns>
        private string GetPageFilePath(HttpRequest request)
        {
            string url = request.Url;
            if (string.IsNullOrWhiteSpace(url))
            {
                return null;
            }

            string filePath = (string)this._urlSourceFilePathCache.Get(url);
            if (!string.IsNullOrWhiteSpace(filePath))
            {
                return filePath;
            }

            lock (this._urlSourceFilePathCache)
            {
                filePath = (string)this._urlSourceFilePathCache.Get(url);
                if (!string.IsNullOrWhiteSpace(filePath))
                {
                    return filePath;
                }

                var urlCacheMilliseconds = this._config.UrlCacheMilliseconds;
                if (urlCacheMilliseconds <= 0)
                {
                    //默认缓存10分钟
                    urlCacheMilliseconds = 1000 * 60 * 10;
                }

                if (url.Length == 1 &&
                    (url.First() == Path.AltDirectorySeparatorChar || url.First() == Path.DirectorySeparatorChar))
                {
                    //请求地址为根目录,则使用默认页面
                    filePath = this._config.DefaultPageFilePath;
                    this._urlSourceFilePathCache.Add(url, filePath, urlCacheMilliseconds);
                    return filePath;
                }


                //文本内容类的需要将内容读取出来
                string fileName = url.Replace(PathEx.RightSlash, PathEx.LeftSlash);
                filePath = PathEx.Combine(PathType.Local, this._config.RootDir, fileName);

                //到根目录中找资源
                if (File.Exists(filePath))
                {
                    //根目录中找到请求的资源
                    this._urlSourceFilePathCache.Add(url, filePath, urlCacheMilliseconds);
                    return filePath;
                }

                //到其它资源目录中找资源
                if (this._config.SourceDirs == null)
                {
                    return null;
                }

                foreach (string sourceDir in this._config.SourceDirs)
                {
                    filePath = PathEx.Combine(PathType.Local, sourceDir, fileName);
                    if (File.Exists(filePath))
                    {
                        //资源目录中找到请求的资源
                        this._urlSourceFilePathCache.Add(url, filePath, urlCacheMilliseconds);
                        return filePath;
                    }
                }
            }

            return null;
        }


        /// <summary>
        /// Dispose
        /// </summary>
        /// <param name="disposing">disposing</param>
        protected override void Dispose(bool disposing)
        {
            base.Dispose(disposing);
            this._urlSourceFilePathCache.Dispose();
        }
    }
}
